﻿#pragma once

// ANIP_FIXME: hide details of the template classes to reduce include
// overhead

#include "common.h"
#include "ui/ui.h"
#include "texture.h"
#include "resource.h"
#include <map>
#include <vector>

// gcc/clang requires everything mentioned in the templates be included
#include "font.h"
#include "draw.h"

namespace anip
{
	class Texture2D;

	namespace ui
	{
		template<class C>
		class WithText : public C
		{
			anip_control_ornament_class;

		private:
			Texture2D* _textTex = nullptr;
			vec2 _textSize;

		protected:
			using C::_font;
			using C::_text;
			
			virtual int getTextWidth() { return -1; }

			virtual void discardTextTexture()
			{
				if (_textTex != nullptr)
				{
					_textTex->finalize();
					delete _textTex;
					_textTex = nullptr;
				}
			}

			virtual void renderText(Texture2D*& tex, outvec2 size)
			{
				if (_font.isNull()) return;
				TextRenderer::Options op;
				op.maxWidth = getTextWidth();
				auto layout = TextRenderer::layoutPixelPerfectString(_text, _font, op);
				tex = TextRenderer::renderString(*layout);
				size = layout->drawingSize();
				delete layout;
			}

			Texture2D* getTextTexture()
			{
				if (_textTex == nullptr) renderText(_textTex, _textSize);
				return _textTex;
			}

			vec2 getTextSize()
			{
				if (_textTex == nullptr)
				{
					_textSize = { 0, 0 };
					renderText(_textTex, _textSize);
				}
				return _textSize;
			}

		public:
			using C::C;

			~WithText()
			{
				discardTextTexture();
				//C::~C();
			}

			bcolor textColor = r::TextColor;

			void setText(const wstring& text) override
			{
				_text = text;
				discardTextTexture();
			}

			void setFont(const Font& font) override
			{
				C::setFont(font);
				discardTextTexture();
			}
		};

		template<class C>
		class Scrollable : public C
		{
			anip_control_ornament_class;

		protected:
			using C::_hoveredChild;
			using C::_size;
			using C::_margin;
			using C::_marginDown;
			using C::_maxScroll;
			using C::_absPos;
		
			void processScroll(invec2 pos) override
			{
				if (onScroll) onScroll(*this, pos);

				if (_hoveredChild == nullptr && userCanScroll)
					scroll = scroll() - vec2{ 0, pos.y } * r::ScrollSpeed();

				C::processScroll(pos);
			}

			void drawScrollers()
			{
				color c = r::Scroller_Color;
				float s = r::Scroller_Size;
				vec2 nsize = _size - _margin - _marginDown;
				if (_maxScroll.x > 0)
				{
					float w = nsize.x * nsize.x / (_maxScroll.x + nsize.x);
					float x = scroll().x / _maxScroll.x * (nsize.x - w);
					Draw::rect(_absPos + _margin + vec2{ x, nsize.y - s }, { w, s }, c);
				}
				if (_maxScroll.y > 0)
				{
					float h = nsize.y * nsize.y / (_maxScroll.y + nsize.y);
					float y = scroll().y / _maxScroll.y * (nsize.y - h);
					Draw::rect(_absPos + _margin + vec2{ nsize.x - s, y }, { s, h }, c);
				}
			}

		public:
			using C::C;
			std::function<void(Scrollable& self, invec2 pos)> onScroll;

			bool userCanScroll = true;

			using Control::scroll;
			using Control::maxScroll;
		};

		template<class C>
		class Draggable : public C
		{
			anip_control_ornament_class;

		protected:
			bool canBeginDrag(MouseButton btn, invec2 pos) override
			{
				if (onBeginDrag)
					return onBeginDrag(*this, btn, pos);
				return C::canBeginDrag(btn, pos);
			}

			void processDrag(invec2 start, invec2 pos) override
			{
				if (onDrag)
					onDrag(*this, start, pos);
				C::processDrag(start, pos);
			}

			void processEndDrag() override
			{
				if (onEndDrag)
					onEndDrag(*this);
				C::processEndDrag();
			}

		public:
			using C::C;
			std::function<bool(Draggable& self, MouseButton btn, invec2 pos)> onBeginDrag;
			std::function<void(Draggable& self, invec2 start, invec2 pos)> onDrag;
			std::function<void(Draggable& self)> onEndDrag;

			using Control::isDragging;
		};

		template<class C>
		class Selectable
		{
			//friend class C;

		protected:
			bool _selected = false;

			C& _container;

			Selectable(C& parent) :
				_container(parent)
			{}

		public:
			bool isSelected() const { return _selected; }

			virtual void setSelected(bool val)
			{
				if (_selected == val) return;
				_selected = val;
				_container.postSignal({ ui::Signal::Type::NeedsRedraw });
			}

			virtual void select(bool val = true)
			{
				if (_selected != val && _container.allowSelect)
				{
					int nSel = 0;
					auto& items = _container.items;
					for (auto& i : items) if (i._selected) nSel++;
					if (!_container.allowZeroSelect && nSel == 1 && _selected)
						return;
					if (!_container.allowMultipleSelect && nSel > 0 && !_selected)
						for (auto& i : items) i._selected = false;

					setSelected(val);
				}
			}

			void toggle() { select(!_selected); }
		};

		template<class C, class I>
		class SelectableContainer : public C
		{
			anip_control_ornament_class;

		public:
			std::vector<I> items;

			bool allowSelect = true;
			bool allowMultipleSelect = false;
			bool allowZeroSelect = false;

			using C::C;

			int selectedIndex() const
			{
				int ind = 0;
				for (auto& i : items)
				{
					if (i.isSelected())
						return ind;
					ind++;
				}
				return -1;
			}
		};


		class Label : public WithText<Control>
		{
		public:
			using WithText::WithText;

			void paint() override;
			void flow(Flow& f) override;

			vec2 align = { 0, 0 };

			// ANIP_TODO: add getTextWidth, detailed autosize etc
		};
	}
}